package com.baymax.study.redisson.infrastructure.locks.aop;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baymax.study.redisson.infrastructure.locks.TaskBusyException;
import com.baymax.study.redisson.infrastructure.locks.annotation.GlobalLock;
import com.baymax.study.redisson.infrastructure.locks.annotation.GlobalLockKey;
import com.baymax.study.redisson.infrastructure.locks.annotation.GlobalLockParam;
import com.baymax.study.redisson.infrastructure.locks.framework.DistributedReadWriteLock;
import com.baymax.study.redisson.infrastructure.locks.utils.GlobalLockUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Slf4j
@Order(value = Integer.MIN_VALUE)
public class LockAspect {

    @Autowired
    private DistributedReadWriteLock distributedReadWriteLock;

    @Pointcut("@annotation(com.baymax.study.redisson.infrastructure.locks.annotation.GlobalLock)")
    public void normalPointcut() {
    }

    @Pointcut("@within(com.baymax.study.redisson.infrastructure.locks.annotation.UnLock))||@annotation(com.baymax.study.redisson.infrastructure.locks.annotation.DxUnLock))")
    public void excludePointcut() {
    }

    @Pointcut("!excludePointcut() && normalPointcut()")
    public void pointcut() {
    }

    @Around(value = "pointcut()")
    public Object doGlobalLock(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> targetClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        Class<?>[] parTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Method objMethod = ClassUtil.getDeclaredMethod(targetClass, methodName, parTypes);
        GlobalLock annotation = AnnotationUtils.findAnnotation(objMethod, GlobalLock.class);
        if (annotation == null) {
            return joinPoint.proceed();
        }

        boolean iswrite = false;
        String name = annotation.name();
        Object result;
        try {
            int keyCount = 0;

            StringBuilder key = new StringBuilder(annotation.key());
            if (!annotation.noParams()) { // 方法没有参数，默认有入参
                if (key.toString().isEmpty()) { // 注解中未指定key
                    // 获取方法入参
                    boolean hasParams = false;
                    Annotation[][] parameterAnnotations = objMethod.getParameterAnnotations();
                    while (!hasParams) {
                        for (Annotation[] parameterAnnotation : parameterAnnotations) {
                            for (Annotation an : parameterAnnotation) {
                                if (an.annotationType().isAssignableFrom(GlobalLockParam.class)) {
                                    hasParams = true;
                                }
                            }
                        }
                        if (!hasParams) {
                            try {
                                targetClass = targetClass.getSuperclass();
                                objMethod = targetClass.getMethod(methodName, parTypes);
                            } catch (NoSuchMethodException exception) {
                                log.error("方法入参没有DxGlobalLockParam注解,或者将该注解写在了接口上{}", methodName);
                                throw new RuntimeException("方法入参没有DxGlobalLockParam注解,或者将该注解写在了接口上");
                            }
                            parameterAnnotations = objMethod.getParameterAnnotations();
                        }
                    }
                    for (int i = 0; i < parameterAnnotations.length; i++) {
                        //
                        for (Annotation an : parameterAnnotations[i]) {
                            if (an.annotationType().isAssignableFrom(GlobalLockParam.class)) {
                                keyCount++;
                                // 获取入参
                                GlobalLockParam globalLockParam = (GlobalLockParam) an;
                                Object params = joinPoint.getArgs()[i];
                                if (params == null) {
                                    continue;
                                }
                                Class<?> paramsClass = params.getClass();
                                boolean wrapClass = GlobalLockUtils.isWrapClass(paramsClass);
                                if (wrapClass) {
                                    key.append(params);
                                } else {
                                    String filedValue = globalLockParam.value();
                                    Object okey = null;
                                    if (filedValue.length() > 1) {
                                        //是否包含. 包含点代表取属性类的属性
                                        if (filedValue.contains(".")) {
                                            String[] split = filedValue.split("\\.");
                                            Object targetObject = params;
                                            for (int k = 0; k < split.length; k++) {
                                                // 反射获取
                                                Field field = paramsClass.getDeclaredField(split[k]);
                                                field.setAccessible(true);
                                                if (k == (split.length - 1)) {
                                                    okey = field.get(targetObject);
                                                } else {
                                                    targetObject = field.get(targetObject);
                                                    paramsClass = targetObject.getClass();
                                                }
                                            }
                                        } else {
                                            // 反射获取
                                            Field field = null;
                                            while (field == null) {
                                                try {
                                                    field = paramsClass.getDeclaredField(filedValue);
                                                } catch (NoSuchFieldException noSuchFieldException) {
                                                    paramsClass = paramsClass.getSuperclass();
                                                    if (paramsClass.getName().equals(Object.class.getName())) {
                                                        log.error("没有找到类要锁住的属性{}", filedValue);
                                                        throw new RuntimeException("没有找到类要锁住的属性");
                                                    }
                                                }
                                            }
                                            field.setAccessible(true);
                                            okey = field.get(params);
                                        }
                                        if (okey != null) {
                                            if (GlobalLockUtils.isWrapClass(okey.getClass())) {
                                                key.append(okey);
                                            } else {
                                                key.append(JSON.toJSONString(okey));
                                            }
                                        }
                                    } else {
                                        // 获取反射注解
                                        Field[] fields = paramsClass.getDeclaredFields();
                                        for (Field field : fields) {
                                            Annotation annotation2 = field.getAnnotation(GlobalLockKey.class);
                                            if (annotation2 != null) {
                                                field.setAccessible(true);
                                                Object o = field.get(params);
                                                if (o != null) {
                                                    if (GlobalLockUtils.isWrapClass(o.getClass())) {
                                                        key.append(o);
                                                    } else {
                                                        key.append(JSON.toJSONString(o));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                    }
                }
            }
            if (StrUtil.isEmpty(name)) {
                name = targetClass.getName() + joinPoint.getSignature().getName();
            }
            String newKey = key.toString();
            if (keyCount > 1) {
                newKey = GlobalLockUtils.sort(newKey);
            }
            name = GlobalLockUtils.buildLockValue(name, newKey);
            if (annotation.isWrite()) {
                iswrite = true;
                if (distributedReadWriteLock.getWriteLock().tryLock(name, annotation.waitTimeoutMillisecond(), annotation.timeoutMillisecond(), TimeUnit.MILLISECONDS)) {
                    result = joinPoint.proceed();
                } else {
                    log.warn("等待写锁超时{}---{}", name, methodName);
                    throw new TaskBusyException("任务处理中，请稍后重试~" + name);
                }
            } else {
                if (distributedReadWriteLock.getReadLock().tryLock(name, annotation.waitTimeoutMillisecond(), annotation.timeoutMillisecond(), TimeUnit.MILLISECONDS)) {
                    result = joinPoint.proceed();
                } else {
                    log.warn("等待读锁超时{}---{}", name, methodName);
                    throw new TaskBusyException("任务处理中，请稍后重试~" + name);
                }
            }
        } catch (Exception unknownError) {
            if (!(unknownError instanceof TaskBusyException)) {
                log.error(unknownError.getMessage(), unknownError);
            }
            throw unknownError;
        } finally {
            if (name != null) {
                if (iswrite) {
                    if (distributedReadWriteLock.getWriteLock().isLocked(name) && distributedReadWriteLock.getWriteLock().isHeldByCurrentThread(name)) {
                        distributedReadWriteLock.getWriteLock().unlock(name);
                    }
                } else {
                    if (distributedReadWriteLock.getReadLock().isLocked(name) && distributedReadWriteLock.getReadLock().isHeldByCurrentThread(name)) {
                        distributedReadWriteLock.getReadLock().unlock(name);
                    }
                }
            }
        }
        return result;
    }
}
